/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "elf.hh"
#include "file_mng.hh"
#include "string.hh"

#include <bfd.h>
#include <elf.h>
#include <errno.h>
#include <string.h>

// DEFINITIONS
/* demangle options copied from: /usr/src/binutils-2.18.1~cvs20080103/include/demangle.h */
#ifndef    DMGL_PARAMS
#   define DMGL_NO_OPTS     0              /* For readability... */
#   define DMGL_PARAMS      (1 << 0)       /* Include function args */
#   define DMGL_ANSI        (1 << 1)       /* Include const, volatile, etc */
#   define DMGL_JAVA        (1 << 2)       /* Demangle as Java rather than C++. */
#   define DMGL_VERBOSE     (1 << 3)       /* Include implementation details.  */
#   define DMGL_TYPES       (1 << 4)       /* Also try to demangle type encodings.  */
#   define DMGL_RET_POSTFIX (1 << 5)       /* Print function return types (when
                                              present) after function signature */
#endif

/* demangle proto copied from: /usr/src/binutils-2.18.1~cvs20080103/include/demangle.h */
#ifndef HAVE_DEMANGLE
extern "C" char *cplus_demangle (const char *mangled, int options);
#endif

CLASS_INIT(elf); // ELF CLASS

elf::elf()
{
    CLASS_CTOR(elf);
    ctor();
}

elf::~elf()
{
    CLASS_DTOR(elf);
    dtor();
}

void elf::ctor()
{
    elf_mem=NULL;
    n_elf_mem=0;

    elf_filename=NULL;
}

void elf::dtor()
{
    free_elf();
    if(elf_filename) elf_filename=(char*)realloc(elf_filename, 0);
}

bool elf::init()
{
    if(elf_filename && !open_elf()) ERROR_RX(false, "opening ELF file {%s}", elf_filename);
    return true;
}

void elf::free_elf()
{
    if(elf_mem) elf_mem=(char*)realloc(elf_mem, 0);
    n_elf_mem=0;
}

bool elf::open_elf()
{
    free_elf();
    int max=0;
    if(!file_mng::load_n_max_nf<char>(elf_mem, n_elf_mem, max, elf_filename))
	ERROR_RX(false, "loading ELF file: %s", strerror(errno));
    return true;
}

bool elf::set_elf_file(char *filename)
{
    if(elf_filename) elf_filename=(char*)realloc(elf_filename, 0);
    if(filename) elf_filename=strdup(filename);
    if(elf_filename && !open_elf()) ERROR_RX(false, "opening ELF file {%s}", elf_filename);
    return true;
}

bool elf::dump()
{
    if(!elf_mem) ERROR_RX(false, "No ELF loaded");

    Elf32_Ehdr *elf_header;

    // check elf type
    elf_header=(Elf32_Ehdr *)elf_mem;
    if(memcmp(elf_mem, ELFMAG, SELFMAG) ||
       elf_mem[EI_CLASS]!=ELFCLASS32 ||
       elf_header->e_machine!=EM_386) ERROR_RX(false, "Not a 32bit i386 ELF file: TODO support other file formats");

    // size
    printf(WHITE"ELF size: "RESET"%d\n", n_elf_mem);

    // sections
    int section_off=elf_header->e_shoff;
    int section_siz=elf_header->e_shentsize;
    int section_num=elf_header->e_shnum;
    int section_str=elf_header->e_shstrndx;
    Elf32_Shdr *section_string=(Elf32_Shdr *)&elf_mem[section_off+section_siz*section_str];

    for(int i=0; i<section_num; i++)
    {
	Elf32_Shdr *section=(Elf32_Shdr *)&elf_mem[section_off+section_siz*i];
	printf(WHITE"Section %2d: "RESET"%9d %s %s\n", i, section->sh_size,
	       (section->sh_type==SHT_SYMTAB)?"SYM":(section->sh_type==SHT_STRTAB)?"STR":"   ",
	       &elf_mem[section_string->sh_offset + section->sh_name]);
    }

    return true;
}

bool elf::get_symbols(char *file_name, string_index *symtab, int demangle)
{
    bool dev=false;
    bfd *elf_file;
    bfd_init();
    if((elf_file=bfd_openr(file_name, NULL/*target*/)))
    {
	// check format
	if(!bfd_check_format(elf_file, bfd_archive))
	{
	    if(bfd_check_format_matches(elf_file, bfd_object, NULL))
	    {
		// get dymanic symbols
		int upper_bound;
		if((upper_bound=bfd_get_dynamic_symtab_upper_bound(elf_file))>0)
		{
		    asymbol **symbols;
		    int n_symbols;
		    symbols=(asymbol **)malloc(upper_bound);

		    if((n_symbols=bfd_canonicalize_dynamic_symtab(elf_file, symbols))>=0)
		    {
			int i;
			for(i=0; i<n_symbols; i++)
			    if(symbols[i]->name) add_symbol(elf_file, symtab, const_cast<char*>(symbols[i]->name), demangle);
			    else {ERROR("null dynamic symbol"); break;}
			if(i==n_symbols) dev=true;
		    }
		    else ERROR("Internal: no dynamic symbols at this stage");

		    // free memory, close elf and exit
		    free(symbols);
		}
		//else MSG("%s: no dynamic symtab", file_name);

		// get symbols
		if((upper_bound=bfd_get_symtab_upper_bound(elf_file))>0)
		{
		    asymbol **symbols;
		    int n_symbols;
		    symbols=(asymbol **)malloc(upper_bound);

		    if((n_symbols=bfd_canonicalize_symtab(elf_file, symbols))>=0)
		    {
			int i;
			for(i=0; i<n_symbols; i++)
			    if(symbols[i]->name) add_symbol(elf_file, symtab, const_cast<char*>(symbols[i]->name), demangle);
			    else {ERROR("null symbol"); break;}
			if(i==n_symbols) dev=true;
		    }
		    else ERROR("Internal: no symbols at this stage");

		    // free memory, close elf and exit
		    free(symbols);
		}
		//else MSG("%s: no symtab", file_name);
	    }
	    else MSG("%s: not an ELF file", file_name);
	}
	else MSG("%s: TODO: archive format", file_name);

	if(!bfd_close(elf_file)) ERROR("bfd_close error");
    }
    else ERROR("%s: bfd_openr error", file_name);
    return dev;
}

void elf::add_symbol(bfd *elf_file, string_index *symtab, char *mangled, int demangle)
{
#ifdef HAVE_DEMANGLE
    char *demangled=demangle?bfd_demangle(elf_file, mangled, DMGL_ANSI | DMGL_PARAMS /*| DMGL_VERBOSE | DMGL_TYPES*/):NULL;
#else
    char *demangled=demangle?cplus_demangle(mangled, DMGL_ANSI | DMGL_PARAMS /*| DMGL_VERBOSE | DMGL_TYPES*/):NULL;
#endif

    if(demangled)
    {
	symtab->search_add_string_format("%s%c%s", mangled, ELF_SEPARATOR_CHARACTER, demangled);
	free(demangled);
    }
    else
	symtab->search_add_string(mangled);
}
